package SimpleAnimator
{
	import mx.core.IVisualElement;
	import mx.events.EffectEvent;
	import spark.effects.Move;
	import spark.effects.easing.Linear;
	
	public class SimpleAnimator implements ILRAnimator
	{
		public function SimpleAnimator() { ; }

		private var m_defaultTilesToShow:uint = 6;
		private var m_defaultTilesToShowVariance:uint = 2;
		private var m_defaultTileDurationMS:uint = 15000;
		private var m_defaultTileDurationMSVariance:uint = 5000;
		
		private var m_tileMovers:Array;
		private var m_tileIndex:uint;
		
		public function initialize(mgr:LRCommanderMgr):void
		{
			m_mgr = mgr;
			m_tileMovers = new Array;
			for( var i:uint = 0; i < m_defaultTilesToShow+m_defaultTilesToShowVariance; i++ )
			{
				var mover:Move = new Move;
				mover.easer = new spark.effects.easing.Linear;
				m_tileMovers.push(mover);
			}
		}
		
		private var m_mgr:LRCommanderMgr;
			
		//
		// The requirements for animators are documented in the ILRAnimator.as interface definition.
		//
		
		private var m_animatorState:String = "Stopped";
		private var m_prePauseAnimatorState:String;
		public function get animationState():String { return m_animatorState; }

		public function startAnimation():void
		{
			switch( m_animatorState )
			{
				case "Running":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_HIGH, "startAnimation: the animation is already running");
					return;

				case "Stopping":			
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_HIGH, "startAnimation: can't start while stopping" );
					return;
			
				case "Paused":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "startAnimation: resuming from pause" ); 

					for( var i:uint = 0; i < m_tileMovers.length; i++ )
						(m_tileMovers[i] as Move).resume();
					
					m_animatorState = m_prePauseAnimatorState;
					break;
				
				case "Stopped":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "startAnimation: starting animation" ); 

					positionTiles();
					animateTiles();
					m_animatorState = "Running";
					break;
			
				default:
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_URGENT, "startAnimation: the animator is in an unknown state:", m_animatorState); 
					return;	
			}
		}

		private function positionTiles():void
		{
			// Don't let tiles be larger than 1/4 of the width of the screen, or 1/3 of the height.
			var m_maxTileWidth:Number = m_mgr.animationSurface.width / 4;
			var m_maxTileHeight:Number = m_mgr.animationSurface.height / 3;

			TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "positionTiles: maxTileW=",m_maxTileWidth,"maxTileH=",m_maxTileHeight ); 
			
			// Walk the tile list and size and position the tiles.
			for( var i:uint = 0; i < m_mgr.tileList.length; i++ )
			{
				var tile:IVisualElement = m_mgr.tileList[i] as IVisualElement;
				m_mgr.animationSurface.addElement(tile);
				
				// Figure out if it needs to be scaled down to fit within the max size constraints.
				var xScaleFactor:Number = 1;
				var yScaleFactor:Number = 1;
								
				if( tile.width > m_maxTileWidth )
					xScaleFactor = m_maxTileWidth / (tile.width as Number);
				if( tile.height > m_maxTileHeight )
					yScaleFactor = m_maxTileHeight / (tile.height as Number);
				
				var scaleFactor:Number = Math.min( xScaleFactor, yScaleFactor);
				var scaledW:uint = tile.width * scaleFactor;
				var scaledH:uint = tile.height * scaleFactor;

				TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "positionTiles: scaled w=",scaledW,"scaled h=",scaledH); 
					
				tile.width = scaledW; 
				tile.height = scaledH;

				// Place it in a semi-randomized start position.
				setTileStartPosition(tile);
			}
		}

		private function setTileStartPosition(tile:IVisualElement):void
		{
			// Place it at a random x position, and just off the top edge of the animation surface.
			var xPos:int = Utils.randomNumber(0,(m_mgr.animationSurface.width - tile.width));
			var yPos:int = -tile.height;
			
			TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "setTilePosition: x=",xPos,"y=",yPos); 

			tile.x = xPos;
			tile.y = yPos;
		}
			
		private function animateTiles():void
		{
			var i:uint;

			// We always remove all effect event listeners when we stop and re-add them when we start.
			for(i = 0; i < m_tileMovers.length; i++ )
				(m_tileMovers[i] as Move).addEventListener(EffectEvent.EFFECT_END, doNextMove);

			// Reset the tile index.
			m_tileIndex = 0;
			
			var tilesToShow:uint = m_defaultTilesToShow + Utils.randomNumber(-m_defaultTilesToShowVariance, m_defaultTilesToShowVariance);
			if( tilesToShow > m_mgr.tileList.length )
				tilesToShow = m_mgr.tileList.length;
			
			// Start the first batch of tiles.  As these complete, the EFFECT_END handler will pick up and start the next one.
			var interval:uint = m_defaultTileDurationMS / tilesToShow;
			for(i = 0; i < tilesToShow; i++ )
			{
				// n.b. You *have* to set the startDelay first, or it doesn't get picked up by the effect instances.
				(m_tileMovers[i] as Move).startDelay = (interval*i) + Utils.randomNumber(-interval/4, interval/4);
				applyMoverToNextTile( m_tileMovers[i] as Move );
			}
		}

		private function applyMoverToNextTile(mover:Move):void
		{
			TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "applyMoverToNextTile: processing tile number ",m_tileIndex);

			mover.yFrom = m_mgr.tileList[m_tileIndex].y;
			mover.yTo = m_mgr.animationSurface.height;
			mover.duration = m_defaultTileDurationMS + Utils.randomNumber(-m_defaultTileDurationMSVariance, m_defaultTileDurationMSVariance);
			mover.target = m_mgr.tileList[m_tileIndex];
			mover.play();
			
			m_tileIndex = ++m_tileIndex % m_mgr.tileList.length;		
		}
		
		private function doNextMove(e:EffectEvent):void
		{
			// If we're stopped or paused, these events shouldn't be firing.
			switch( m_animatorState )
			{
				case "Stopped":
				case "Paused":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_URGENT, "doNextMove: we shouldn't be getting an EFFECT_END while stopped or paused");
					break;

				case "Stopping":
					// Don't schedule any more movers if we're stopping... and if there are no more effects running, transition to the "Stopped" state.
					var allStopped:Boolean = true;
					var i:uint;
					for( i = 0; i < m_tileMovers.length; i++ )
					{
						if( (m_tileMovers[i] as Move).isPlaying )
							allStopped = false;
					}
					
					if( allStopped )
						completeStopProcessing();
					else
						TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "doNextMove: still waiting for animation end to finish");
		
					break;
				
				case "Running":
					// Give this tile a new semi-random start position for next time.
					var mover:Move = e.target as Move;
					setTileStartPosition(mover.target as IVisualElement);

					// Clear the start delay for this mover, then schedule the next tile on this mover.
					mover.startDelay = 0;
					applyMoverToNextTile(mover);
					break;

				default:
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_URGENT, "doNextMove: the animator is in an unknown state:", m_animatorState); 
					return;
			}
		}
		
		private function completeStopProcessing():void
		{
			TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "completeStopProcessing: full stop now");
			
			// Stop all the animators and remove the event listeners.
			for( var i:uint = 0; i < m_tileMovers.length; i++ )
			{
				(m_tileMovers[i] as Move).stop();
				(m_tileMovers[i] as Move).removeEventListener(EffectEvent.EFFECT_END, doNextMove);
			}
			
			// Remove all the tiles from the animation surface.
			m_mgr.animationSurface.removeAllElements();
			
			// Remove any scale factor we applied to the tiles.
			for( i = 0; i < m_mgr.tileList.length; i++ )
			{
				(m_mgr.tileList[i] as IVisualElement).width = NaN;
				(m_mgr.tileList[i] as IVisualElement).height = NaN;
			}
			
			// Set the animator state to stopped.
			m_animatorState = "Stopped";	
		}
		
		public function pauseAnimation():void
		{
			switch( m_animatorState )
			{
				case "Running":
				case "Stopping":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "pauseAnimation: pausing"); 

					for( var i:uint = 0; i < m_tileMovers.length; i++ )
						(m_tileMovers[i] as Move).pause();
					
					m_prePauseAnimatorState = m_animatorState;
					m_animatorState = "Paused";
					break;
				
				case "Paused":				
				case "Stopped":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "pauseAnimation: ignoring pause request while paused or stopped"); 
					break;
				
				default:
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_URGENT, "pauseAnimation: the animator is in an unknown state:", m_animatorState); 
					return;
			}
		}


		public function stopAnimation(stopImmediately:Boolean = false):void
		{
			if( stopImmediately )
			{
				completeStopProcessing();
				return;
			}
			
			// Otherwise, follow the normal state machine.
			switch( m_animatorState )
			{
				case "Running":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "stopAnimation: entering Stopping state"); 
					m_animatorState = "Stopping";
					break;
				
				case "Stopping":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "stopAnimation: ignoring call to stop while Stopping"); 
					break;
				
				case "Paused":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "stopAnimation: unpausing and stopping");
					startAnimation();
					stopAnimation();
					break;
				
				case "Stopped":
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_MEDIUM, "stopAnimation: already stopped");
					break;
				
				default:
					TraceManager.trace_msg(TraceManager.SIMPLE_ANIMATOR, TraceManager.TL_URGENT, "stopAnimation: the animator is in an unknown state:", m_animatorState); 
					return;
			}
		}	
	}
}